#! /usr/bin/env python
"""
How to host a dungeon QT4 plotter
"""

import math
import random
import sys
import time

from pprint import pprint

from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import Qt

# Globals
ANIMATIONS = False # Future Pi in the sky
INTERVAL = 200 # ms
GRIDSIZE = 50.0
MINORGRID = GRIDSIZE / 5.0
DUNGEONWIDTH = 22
DUNGEONHEIGHT = 17
SKYHEIGHT = 4
COLORS = {
    'cave':'darkgrey',
    'cavern':'grey',
    'crack':'green',
    'entrance':'grey',
    'gold':'yellow',
    'hall':'white',
    'lavashaft':'darkred',
    'magma':'darkred',
    'mithril':'white',
    'river':'blue',
    'water':'blue',
    'rock':'grey',
    'sky':'lightblue',
    'tunnel':'black',
    'treasure':'yellow',
    }
DIRECTIONS = ['UP', 'UPRIGHT', 'RIGHT', 'DOWNRIGHT', 'DOWN', 'DOWNLEFT', 'LEFT', 'UPLEFT']
TRAILS = ['tunnel', 'river', 'magma', 'gold', 'crack', 'mithril', 'lavashaft']
BACKGROUNDS = ['sky', 'rock', 'surface']
ROOMS = ['cavern', 'cave', 'hall', 'lake']
SURFACEROOMS = ['entrance']
CREATURES = []
OBJECTS = []

class DungeonGrid(QtGui.QGraphicsItem):
    
    def __init__(self, xGrid, yGrid, color='grey'):
        super(DungeonGrid, self).__init__()
        self.rect = QtCore.QRectF(0, 0, GRIDSIZE, GRIDSIZE)
        self.name = 'DungeonGrid'
        self.color = QtGui.QColor(QtCore.QString(color))
        self.location = [xGrid, yGrid]
        self.setPos(xGrid * GRIDSIZE, yGrid * GRIDSIZE)
        self.background = None
        self.trails = []
        self.room = None
        self.objects = []
        self.creatures = []
        self.path = QtGui.QPainterPath()
        self.path.addRect(self.rect)

    def boundingRect(self):
        return self.rect

    def shape(self):
        return self.path

    def setBackground(self, background):
        if isinstance(background, QtGui.QGraphicsItem):
            try:
                del self.background
            except:
                pass
            background.setParentItem(self)
            background.setZValue(-5)
            self.background = background
        elif callable(background):
            back = background()
            back.setParentItem(self)
            back.setZValue(-5)
            self.background = back
    
    def addTrail(self, trail):
        if isinstance(trail, QtGui.QGraphicsItem):
            trail.setParentItem(self)
            trail.setZValue(-2)
            self.trails.append(trail)
    
    def setRoom(self, room):
        if isinstance(room, QtGui.QGraphicsItem):
            room.setParentItem(self)
            room.setZValue(0)
            self.room = room
    
    def addObject(self, dungeonObject):
        if isinstance(dungeonObject, QtGui.QGraphicsItem):
            dungeonObject.setParentItem(self)
            self.objects.append(dungeonObject)
            xPos = 1 + (len(self.objects) - 1) % 3
            yPos = 1 + (len(self.objects) - 1) / 3
            dungeonObject.setPos(xPos * MINORGRID, yPos * MINORGRID)
            dungeonObject.setZValue(5)
        elif callable(dungeonObject):
            obj = dungeonObject(self)
            if not isinstance(obj, QtGui.QGraphicsItem):
                print 'Bad callable'
                return False
            self.objects.append(obj)
            xPos = 1 + (len(self.objects) - 1) % 3
            yPos = 1 + (len(self.objects) - 1) / 3
            obj.setPos(xPos * MINORGRID, yPos * MINORGRID)
            obj.setZValue(5)
        
    
    def addCreature(self, creature):
        if isinstance(creature, QtGui.QGraphicsItem):
            creature.setParentItem(self)
            self.creatures.append(creature)
            xPos = 3 - (len(self.creatures) - 1) % 3
            yPos = 3 - (len(self.creatures) - 1) / 3
            creature.setPos(xPos * MINORGRID, yPos * MINORGRID)
            creature.setZValue(10)
        elif callable(creature):
            print 'not here'
            c = creature(self)
            if not isinstance(c, QtGui.QGraphicsItem):
                print 'Bad callable'
                return False
            self.creatures.append(c)
            xPos = 3 - (len(self.creatures) - 1) % 3
            yPos = 3 - (len(self.creatures) - 1) / 3
            c.setPos(xPos * MINORGRID, yPos * MINORGRID)
            c.setZValue(10)

    def paint(self, painter, option, widget=None):
        painter.setPen(self.color)
        painter.drawPath(self.path)
        # if self.background:
        #     self.background.paint(painter, option, widget)
        # for trail in self.trails:
        #     trail.paint(painter, option, widget)
        # if self.room:
        #     self.room.paint(painter, option, widget)
        # for dungeonObject in self.objects:
        #     dungeonObject.paint(painter, option, widget)
        # for creature in self.creatures:
        #     creature.paint(painter, option, widget)
        pass


class DungeonGraphic(QtGui.QGraphicsItem):
    
    def __init__(self, parent=None, color='grey'):
        super(DungeonGraphic, self).__init__(parent)
        self.rect = QtCore.QRectF(0, 0, GRIDSIZE, GRIDSIZE)
        self.name = 'DungeonGraphic'
        self.color = QtGui.QColor(QtCore.QString(color))
        self._createPath()

    def _createPath(self):
        self.path = QtGui.QPainterPath()
        self.path.addRect(self.rect)

    def boundingRect(self):
        return self.rect

    def shape(self):
        return self.path


class DungeonBackground(DungeonGraphic):
    
    def __init__(self, parent=None, backType='rock'):
        color = COLORS.get(backType, COLORS['rock'])
        super(DungeonBackground, self).__init__(parent, color)
        self.backType = backType.lower()
        self.name = 'DungeonBackground'

    def paint(self, painter, option, widget=None):
        if self.backType == 'surface':
            painter.setPen(QtGui.QColor(COLORS['sky']))
            painter.setBrush(QtGui.QBrush(QtGui.QColor(COLORS['sky']), Qt.SolidPattern))
            painter.drawRect(QtCore.QRectF(0, 0, GRIDSIZE, GRIDSIZE / 2.0))
            painter.setPen(QtGui.QColor(COLORS['rock']))
            painter.setBrush(QtGui.QBrush(QtGui.QColor(COLORS['rock']), Qt.SolidPattern))
            painter.drawRect(QtCore.QRectF(0, GRIDSIZE / 2.0, GRIDSIZE, GRIDSIZE / 2.0))
        else:
            painter.setPen(self.color)
            painter.setBrush(QtGui.QBrush(self.color, Qt.SolidPattern))
            painter.drawPath(self.path)


# class Sky(DungeonBackground):
#     
#     def __init__(self, parent=None):
#         super(Sky, self).__init__(parent, COLORS['sky'])
#         self.name = 'Sky'
# 
# 
# class Rock(DungeonBackground):
#     
#     def __init__(self, parent=None):
#         super(Rock, self).__init__(parent, COLORS['rock'])
#         self.name = 'Rock'
#         
# 
# class Surface(DungeonBackground):
#     
#     def __init__(self, parent=None):
#         super(Surface, self).__init__(parent)
#         self.name = 'Surface'
#         self.skyColor = QtGui.QColor(QtCore.QString(COLORS['sky']))
#         self.rockColor = QtGui.QColor(QtCore.QString(COLORS['rock']))
#     
#     def paint(self, painter, option, widget=None):
#         painter.setPen(self.skyColor)
#         painter.setBrush(QtGui.QBrush(self.skyColor, Qt.SolidPattern))
#         painter.drawRect(QtCore.QRectF(0, 0, GRIDSIZE, GRIDSIZE / 2.0))
#         painter.setPen(self.rockColor)
#         painter.setBrush(QtGui.QBrush(self.rockColor, Qt.SolidPattern))
#         painter.drawRect(QtCore.QRectF(0, GRIDSIZE / 2.0, GRIDSIZE, GRIDSIZE / 2.0))


class DungeonObject(DungeonGraphic):
    
    def __init__(self, parent=None, name='Dungeon Object', color='black'):
        super(DungeonObject, self).__init__(parent, COLORS.get(name.lower(), 'black'))
        # Objects take 1/25 of a grid square
        self.rect = QtCore.QRectF(0, 0, MINORGRID, MINORGRID)
        self.name = name
        self._createPath()

    def _createPath(self):
        self.path = QtGui.QPainterPath()
        self.path.addEllipse(self.rect)

    def paint(self, painter, option, widget=None):
        painter.setPen(self.color)
        if self.name.lower() in ['treasure', 'water']:
            painter.setBrush(QtGui.QBrush(self.color, Qt.SolidPattern))
        painter.drawPath(self.path)
        painter.setPen(QtGui.QColor('black'))
        font = painter.font()
        if .9 <= option.levelOfDetail < 3:
            font.setPointSizeF(MINORGRID / 2.0)
            painter.setFont(font)
            painter.drawText(self.rect, Qt.AlignCenter, 'I')
        elif option.levelOfDetail >= 3:
            font.setPointSizeF(MINORGRID / 8.0)
            painter.setFont(font)
            painter.drawText(self.rect, Qt.AlignCenter, self.name.replace(' ', '\n'))


class DungeonTrail(DungeonGraphic):
    
    def __init__(self, parent=None, direction='UP', trailType='tunnel'):
        self.trailType = trailType.lower()
        self.direction = direction.upper()
        super(DungeonTrail, self).__init__(parent)
        self.rect = QtCore.QRectF(0, 0, GRIDSIZE, GRIDSIZE)
        self._createPath()

    def _createPath(self):
        self.path = QtGui.QPainterPath()
        # Centering the start line in the grid
        self.path.moveTo(GRIDSIZE / 2.0, GRIDSIZE / 2.0)
        if self.direction == 'UP':
            self.path.lineTo(GRIDSIZE / 2.0, 0)
        elif self.direction == 'UPRIGHT':
            self.path.lineTo(GRIDSIZE, 0)
        elif self.direction == 'RIGHT':
            self.path.lineTo(GRIDSIZE, GRIDSIZE / 2.0)
        elif self.direction == 'DOWNRIGHT':
            self.path.lineTo(GRIDSIZE, GRIDSIZE)
        elif self.direction == 'DOWN':
            self.path.lineTo(GRIDSIZE / 2.0, GRIDSIZE)
        elif self.direction == 'DOWNLEFT':
            self.path.lineTo(0, GRIDSIZE)
        elif self.direction == 'LEFT':
            self.path.lineTo(0, GRIDSIZE / 2.0)
        elif self.direction == 'UPLEFT':
            self.path.lineTo(0, 0)

    def paint(self, painter, option, widget=None):
        self.color = QtGui.QColor(COLORS.get(self.trailType, 'tunnel'))
        self.pen = QtGui.QPen(self.color)
        self.pen.setWidth(2)
        painter.setPen(self.pen)
        # painter.setBrush(QtGui.QBrush(self.color, Qt.SolidPattern))
        painter.drawPath(self.path)


class DungeonRoom(DungeonGraphic):
    
    def __init__(self, parent=None, roomType='cavern', color='black'):
        self.roomType = roomType.lower()
        # color = COLORS.get(self.roomType, 'black')
        super(DungeonRoom, self).__init__(parent, color)
        self.rect = QtCore.QRectF(0, 0, GRIDSIZE, GRIDSIZE)
        self._createPath()

    def _createPath(self):
        self.path = QtGui.QPainterPath()
        if self.roomType in ['cavern', 'hall']:
            self.path.addRect(MINORGRID, MINORGRID, MINORGRID * 3, MINORGRID * 3)
        elif self.roomType in ['cave', 'water']:
            self.path.addEllipse(MINORGRID / 2.0, MINORGRID / 2.0, MINORGRID * 4, MINORGRID * 4)
        elif self.roomType in ['entrance']:
            self.path.moveTo(MINORGRID / 2.0, MINORGRID / 2.0 * 9)
            self.path.lineTo(MINORGRID, MINORGRID)
            self.path.lineTo(MINORGRID * 4, MINORGRID)
            self.path.lineTo(MINORGRID / 2.0 * 9, MINORGRID / 2.0 * 9)
            self.path.lineTo(MINORGRID / 2.0, MINORGRID / 2.0 * 9)
            # self.path.moveTo(MINORGRID * 2, GRIDSIZE / 2.0)
            # self.path.lineTo(MINORGRID * 2, GRIDSIZE)
            # self.path.moveTo(MINORGRID * 3, GRIDSIZE / 2.0)
            # self.path.lineTo(MINORGRID * 3, GRIDSIZE)

    def paint(self, painter, option, widget=None):
        painter.setPen(self.color)
        painter.setBrush(QtGui.QBrush(QtGui.QColor(COLORS.get(self.roomType, 'cavern')), Qt.SolidPattern))
        painter.drawPath(self.path)
        if self.roomType in ['entrance']:
            painter.setPen(QtGui.QColor('black'))
            painter.setBrush(QtGui.QBrush(QtGui.QColor('black'), Qt.SolidPattern))
            entPath = QtGui.QPainterPath()
            painter.drawPolygon(QtGui.QPolygonF([QtCore.QPointF(MINORGRID * 2, GRIDSIZE / 2.0),
                                                 QtCore.QPointF(MINORGRID * 2, MINORGRID * 2),
                                                 QtCore.QPointF(MINORGRID * 3, MINORGRID * 2),
                                                 QtCore.QPointF(MINORGRID * 3, GRIDSIZE / 2.0)]))
class DungeonCreature(DungeonGraphic):
    
    def __init__(self, parent=None, name='Dungeon Creature', color='black'):
        super(DungeonCreature, self).__init__(parent, color)
        # Creatures take 1/25 of a grid square
        self.rect = QtCore.QRectF(0, 0, MINORGRID, MINORGRID)
        self.name = name

    def _createPath(self):
        self.path = QtGui.QPainterPath()
        self.path.addPolygon(QtGui.QPolygonF([QtCore.QPointF(0.2 * MINORGRID, 0.0 * MINORGRID),
                                              QtCore.QPointF(0.8 * MINORGRID, 0.0 * MINORGRID),
                                              QtCore.QPointF(1.0 * MINORGRID, 0.5 * MINORGRID),
                                              QtCore.QPointF(0.8 * MINORGRID, 1.0 * MINORGRID),
                                              QtCore.QPointF(0.2 * MINORGRID, 1.0 * MINORGRID),
                                              QtCore.QPointF(0.0 * MINORGRID, 0.5 * MINORGRID)]))
        self.path.closeSubpath()

    def paint(self, painter, option, widget=None):
        painter.setPen(self.color)
        # painter.setBrush(QtGui.QBrush(self.color, Qt.SolidPattern))
        painter.drawPath(self.path)
        font = painter.font()
        if .9 <= option.levelOfDetail < 3:
            font.setPointSizeF(MINORGRID / 2.0)
            painter.setFont(font)
            painter.drawText(self.rect, Qt.AlignCenter, 'M')
        elif option.levelOfDetail >= 3:
            font.setPointSizeF(MINORGRID / 8.0)
            painter.setFont(font)
            painter.drawText(self.rect, Qt.AlignCenter, self.name.replace(' ', '\n'))


class DungeonView(QtGui.QGraphicsView):
    def __init__(self, parent=None):
        super(DungeonView, self).__init__(parent)
        self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)
        self.setRenderHint(QtGui.QPainter.Antialiasing)
        self.setRenderHint(QtGui.QPainter.TextAntialiasing)

    def wheelEvent(self, event):
        factor = 1.41 ** (event.delta() / 120.0)
        self.scale(factor, factor)

    def keyPressEvent(self, event):
        factor = 2.0
        if event.key() == Qt.Key_F7:
            self.scale(factor, factor)
        elif event.key() == Qt.Key_F8:
            self.scale(1 / factor, 1 / factor)
        else: 
            print 'Key %s pressed' % int(event.key()).__hex__()



class DungeonViewer(dict):
    def __init__(self, dungeon=None):
        super(DungeonViewer, self).__init__()
        self.dungeonView = DungeonView()
        self.dungeonView.setMinimumSize(QtCore.QSize(1200, 900))
        self.dungeonScene = QtGui.QGraphicsScene()
        self.dungeonView.setScene(self.dungeonScene)
        if isinstance(dungeon, dict):
            if dungeon.has_key('map'):
                self.plotDungeon(dungeon['map'])
        else:
            self.plotTest()

    def plotDungeon(self, dungeon):
        # print 'plotDungeon not implemented yet'
        # return False
        for colIndex, col in enumerate(dungeon):
            for rowIndex, row in enumerate(col):
                gridSquare = DungeonGrid(rowIndex, colIndex)
                bgFound = False
                for itemIndex, item in enumerate(row):
                    baseItem = item.split()[0].lower()
                    suffix = item.split()[-1].lower()
                    if baseItem in BACKGROUNDS:
                        bgFound = True
                        gridSquare.setBackground(DungeonBackground(backType=baseItem))
                    elif 'dungeon entrance' in item.lower():
                        gridSquare.setRoom(DungeonRoom(roomType='entrance'))
                    elif baseItem in ROOMS:
                        gridSquare.setRoom(DungeonRoom(roomType=baseItem))
                    elif baseItem in TRAILS:
                        gridSquare.addTrail(DungeonTrail(trailType=baseItem, direction=suffix))
                    elif baseItem in CREATURES:
                        gridSquare.addCreature(DungeonCreature(creatureType=item))
                    else:
                        gridSquare.addObject(DungeonObject(name=item))
                if not bgFound:
                    gridSquare.setBackground(DungeonBackground(backType='rock'))
                self.dungeonScene.addItem(gridSquare)

    def plotTest(self):
        colors = ['black', 'blue', 'red', 'green', 'yellow', 'orange', 'pink']
        # self.dungeonScene.addItem(Surface(0, 0))
        # for index, color in enumerate(colors):
        #     self.dungeonScene.addItem(DungeonBackground(index + 1, index + 1, color))
        for xx in range(DUNGEONWIDTH):
            for yy in range(SKYHEIGHT):
                gridSquare = DungeonGrid(xx, yy)
                gridSquare.setBackground(DungeonBackground(backType='sky'))
                for ii in range(random.randint(0, 4)):
                    gridSquare.addCreature(DungeonCreature())
                for ii in range(random.randint(0, 4)):
                    gridSquare.addObject(DungeonObject())
                self.dungeonScene.addItem(gridSquare)
        for xx in range(DUNGEONWIDTH):
            gridSquare = DungeonGrid(xx, SKYHEIGHT)
            gridSquare.setBackground(DungeonBackground(backType='surface'))
            for ii in range(random.randint(0, 4)):
                gridSquare.addCreature(DungeonCreature())
            for ii in range(random.randint(0, 4)):
                gridSquare.addObject(DungeonObject())
            if random.randint(1, 5) == 5:
                gridSquare.setRoom(DungeonRoom(roomType='entrance'))
            self.dungeonScene.addItem(gridSquare)

        for xx in range(DUNGEONWIDTH):
            for yy in range(SKYHEIGHT + 1, DUNGEONHEIGHT):
                gridSquare = DungeonGrid(xx, yy)
                gridSquare.setBackground(DungeonBackground(backType='rock'))
                for ii in range(random.randint(0, 4)):
                    gridSquare.addCreature(DungeonCreature())
                for ii in range(random.randint(0, 4)):
                    gridSquare.addObject(DungeonObject())
                if random.randint(0, 1):
                    gridSquare.addTrail(DungeonTrail(direction=DIRECTIONS[random.randrange(len(DIRECTIONS))],
                                                     trailType=TRAILS[random.randrange(len(TRAILS))]))
                if random.randint(0, 1):
                    gridSquare.setRoom(DungeonRoom(roomType=ROOMS[random.randrange(len(ROOMS))]))
                self.dungeonScene.addItem(gridSquare)

    def show(self):
        self.dungeonView.show()


if __name__ == '__main__':
    dungeons = eval(open('dungeons.pyd').read())
    app = QtGui.QApplication(sys.argv)
    if len(sys.argv[1:]) > 0:
        dv = DungeonViewer(sys.argv[1])
    else:
        dv = DungeonViewer(dungeons[random.randrange(0, len(dungeons))])
    dv.show()
    sys.exit(app.exec_())
